﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using IkeCode.WP7.Toolkit.Base;
using Microsoft.Phone.Shell;
using Newtonsoft.Json;
using IkeCode.WP7.Toolkit.Core;
using System.Windows.Media.Imaging;
using System.Diagnostics;
using System.Windows;
using IkeCode.WP7.Toolkit.HttpRequest;
using IkeCode.WP7.Toolkit.Core.Enum;

namespace IkeCode.WP7.Toolkit.Persist
{
    /// <summary>
    /// Helper that contains an smart engine to save (caching) in App State, and consolidate in Isolated Storage only in app closing/deactivating
    /// </summary>
    public class IcSmartPersistService
    {
        #region ## Properties ##

        /// <summary>
        /// Indicates if actual thread is saving
        /// </summary>
        public bool IsSaving { get; private set; }

        /// <summary>
        /// Gets persist state on Initialize
        /// </summary>
        public PersistState State { get; private set; }

        /// <summary>
        /// Cached objects dictionary
        /// </summary>
        public Dictionary<string, IcSmartPersistCacheModel> CachedObjects { get; private set; }

        /// <summary>
        /// Ccached images dictionary
        /// </summary>
        public Dictionary<string, IcSmartPersistImageCacheModel> CachedImages { get; private set; }

        /// <summary>
        /// Get current cache settings
        /// </summary>
        public IcSmartPersistSettings Settings { get; private set; }

        #endregion ## Properties ##

        #region ## Constructors ##

        /// <summary>
        /// Default constructor
        /// </summary>
        public IcSmartPersistService()
        {
            this.CachedObjects = new Dictionary<string, IcSmartPersistCacheModel>();
            this.CachedImages = new Dictionary<string, IcSmartPersistImageCacheModel>();
            this.Settings = new IcSmartPersistSettings();
        }

        /// <summary>
        /// Constructor with settings
        /// </summary>
        public IcSmartPersistService(IcSmartPersistSettings settings)
            : this()
        {
            this.Settings = settings;
        }

        #endregion ## Constructors ##

        #region ## Smart Storage Service ##

        /// <summary>
        /// Cache all files existing in Isolated Storage
        /// </summary>
        public void Initialize()
        {
            try
            {
                this.CacheAllIsolatedStorageObjects();
                this.CacheAllIsolatedStorageImages();
            }
            catch { }
        }

        #region ## Object Methods ##

        /// <summary>
        /// Check if Objects is Persisted into App State else try to get from Isolated Storage
        /// </summary>
        /// <typeparam name="TInterface">Interface type</typeparam>
        /// <param name="fileName">File name</param>
        /// <returns>bool</returns>
        public bool IsObjectPersisted<TInterface>(string fileName)
        {
            var appState = PhoneApplicationService.Current.State;
            var key = this.GetCacheKey(fileName);
            var hasKey = appState.ContainsKey(key);

            if (!hasKey)
            {
                using (var isf = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    var path = GetPath<TInterface>(fileName);

                    hasKey = isf.FileExists(path);
                }
            }

            return hasKey;
        }

        /// <summary>
        /// Get if object is cached, don't join App State and Isolated Storage
        /// </summary>
        /// <typeparam name="TInterface">Interface type</typeparam>
        /// <returns>bool</returns>
        public bool IsObjectCached<TInterface>(string fileName)
        {
            var result = false;
            if (this.CachedObjects.Count > 0)
            {
                var interfaceName = typeof(TInterface).Name;
                result = this.CachedObjects.ContainsKey(interfaceName);
            }
            return result;
        }

        /// <summary>
        /// Add object to App State/Cache Objects list
        /// </summary>
        /// <typeparam name="TModel">Class</typeparam>
        /// <typeparam name="TInterface">Interface</typeparam>
        /// <param name="obj">Object to save</param>
        /// <param name="fileName">File Name</param>
        /// <param name="cacheOnly">If true, just cache obj and don't persist to ISF</param>
        /// <returns>bool</returns>
        public bool AddObject<TModel, TInterface>(TInterface obj, string fileName, bool cacheOnly = false)
            where TInterface : IIcBase
            where TModel : TInterface, new()
        {
            try
            {
                var isfObj = new List<TInterface> { obj };
                var result = this.AddObjectList<TModel, TInterface>(isfObj, true, fileName, cacheOnly);

                return result;
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// Add list of objects to App State/Cache Objects list
        /// </summary>
        /// <typeparam name="TModel">Class</typeparam>
        /// <typeparam name="TInterface">Interface</typeparam>
        /// <param name="obj">List of Objects to save</param>
        /// <param name="isIncremental">If false, override actual file</param>
        /// <param name="fileName">File Name</param>
        /// <param name="cacheOnly">If true, just cache obj and don't persist to ISF on Persist/PersistAll method</param>
        /// <returns>bool</returns>
        public bool AddObjectList<TModel, TInterface>(List<TInterface> obj, bool isIncremental, string fileName, bool cacheOnly = false)
            where TInterface : IIcBase
            where TModel : TInterface, new()
        {
            try
            {
                var result = true;
                this.IsSaving = true;

                var isfObj = new List<TInterface>(obj);
                if (isIncremental)
                {
                    var temp = this.LoadObjectListInternal<TModel, TInterface>(fileName, 0, cacheOnly, true);
                    isfObj.AddRange(temp);
                }

                this.AddOrUpdateCacheList<TModel, TInterface>(isfObj, fileName, cacheOnly);

                return result;
            }
            catch (Exception) { throw; }
            finally
            {
                this.IsSaving = false;
            }
        }

        /// <summary>
        /// Load list of objects from App State else try get from Isolated Storage
        /// </summary>
        /// <typeparam name="TModel">Class type</typeparam>
        /// <typeparam name="TInterface">Interface type</typeparam>
        /// <param name="fileName">File name</param>
        /// <param name="cacheOnly">If true, get only cached values</param>
        /// <param name="skipFileEmptyException">Skip file empty validation</param>
        /// <returns>List of TInterface</returns>
        public List<TInterface> LoadObjectList<TModel, TInterface>(string fileName, bool cacheOnly = false, bool skipFileEmptyException = true)
            where TInterface : IIcBase
            where TModel : TInterface, new()
        {
            try
            {
                return this.LoadObjectListInternal<TModel, TInterface>(fileName, 0, cacheOnly, skipFileEmptyException);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// Try to delete object just in Cache and AppState
        /// </summary>
        /// <typeparam name="TModel">Model type</typeparam>
        /// <typeparam name="TInterface">Interface type</typeparam>
        /// <param name="obj">TInterface obj</param>
        /// <param name="fileName">File name</param>
        /// <returns>bool</returns>
        public bool DeleteObject<TModel, TInterface>(TInterface obj, string fileName)
            where TInterface : IIcBase
            where TModel : TInterface, new()
        {
            try
            {
                var result = false;

                var objList = this.LoadObjectList<TModel, TInterface>(fileName, true, true);
                objList.Remove(obj);

                result = this.AddObjectList<TModel, TInterface>(objList, false, fileName, false);

                return result;
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// Delete object and file from cache and ISF
        /// </summary>
        /// <typeparam name="TInterface">TInterface type</typeparam>
        /// <param name="fileName">File name</param>
        /// <returns>bool</returns>
        public bool DeleteObjectList<TInterface>(string fileName)
        {
            try
            {
                var appState = PhoneApplicationService.Current.State;
                var key = this.GetCacheKey(fileName);
                var hasKey = appState.ContainsKey(key);
                var isObjCached = this.IsObjectCached<TInterface>();

                if (isObjCached)
                {
                    var iName = typeof(TInterface).Name;
                    this.CachedObjects.Remove(iName);
                }
                if (hasKey)
                {
                    appState.Remove(key);
                }

                var path = GetPath<TInterface>(fileName);
                IsolatedStorageFile.GetUserStoreForApplication().Dispose();
                using (var isf = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    isf.DeleteFile(path);
                }

                return true;
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// Update object
        /// </summary>
        /// <typeparam name="TModel">Model type</typeparam>
        /// <typeparam name="TInterface">Interface type</typeparam>
        /// <param name="id">Object id</param>
        /// <param name="obj">Object to update</param>
        /// <returns>boolean</returns>
        public bool UpdateObject<TModel, TInterface>(Guid id, TInterface obj)
            where TInterface : IIcBase
            where TModel : TInterface, new()
        {
            try
            {
                var result = false;
                this.IsSaving = true;
                var interfaceName = typeof(TInterface).Name;

                var isCached = this.IsObjectCached<TInterface>();
                if (isCached)
                {
                    var cached = this.GetCacheObject<TModel, TInterface>();
                    var itemToUpdate = ((IList)cached.Obj).Cast<TInterface>().FirstOrDefault(i => i.Id == id);
                    itemToUpdate = obj;

                    this.AddOrUpdateCacheList<TModel, TInterface>(cached.Obj, cached.FileName, cached.CacheOnly);
                }

                return result;
            }
            catch (Exception) { throw; }
            finally
            {
                this.IsSaving = false;
            }
        }

        /// <summary>
        /// Method to persist App State/Cached Objects data to Isolated Storage
        /// </summary>
        /// <typeparam name="TModel">Class type</typeparam>
        /// <typeparam name="TInterface">Interface type</typeparam>
        /// <param name="fileName">File name</param>
        /// <returns>bool</returns>
        public bool PersistObject<TModel, TInterface>(string fileName)
            where TInterface : IIcBase
            where TModel : TInterface, new()
        {
            try
            {
                return this.PersistObjectInternal<TModel, TInterface>(fileName, 0);
            }
            catch (Exception)
            {
                throw;
            }
        }

        #endregion ## Object Methods ##

        #region ## Image Methods ##

        /// <summary>
        /// Try get image from cache, otherwise try to cache and return
        /// </summary>
        /// <param name="imageUri">Image Url</param>
        /// <param name="callback">Action callback</param>
        /// <returns>IkeCode.WP7.Toolkit.Persist.IcSmartPersistImageCacheModel</returns>
        public void TryGetImageCacheModel(Uri imageUri, Action<IcSmartPersistImageCacheModel, Exception> callback)
        {
            this.CacheImage(imageUri, (cacheModel, ex) =>
            {
                callback(cacheModel, ex);
            });
        }

        #endregion ## Image Methods ##

        /// <summary>
        /// Method to persist all App State/Object Cache, on OnlyCache flag is false, data to Isolated Storage
        /// </summary>
        /// <returns>bool</returns>
        public bool PersistAll()
        {
            try
            {
                var result = false;
                if (this.CachedObjects.Count > 0)
                {
                    var type = typeof(IcSmartPersistService);
                    var method = type.GetMethod("PersistObjectInternal", BindingFlags.NonPublic | BindingFlags.Instance);
                    var caches = new List<IcSmartPersistCacheModel>(this.CachedObjects.Values.Where(i => !i.CacheOnly).ToList());
                    foreach (var obj in caches)
                    {
                        Type[] typeArgs = { obj.ClassType, obj.InterfaceType };
                        object[] args = { obj.FileName, 0 };

                        var mi = method.MakeGenericMethod(typeArgs);
                        mi.Invoke(this, args);
                    }

                    result = true;
                }

                this.PersistImagesInternal();

                return result;
            }
            catch (Exception)
            {
                throw;
            }
        }

        #endregion ## Smart Storage File ##

        #region ## Isolated Storage Setting ##

        /// <summary>
        /// Load Isolated Storage Settings key
        /// </summary>
        /// <typeparam name="T">Type of return</typeparam>
        /// <param name="key">ISS Key</param>
        /// <returns>T</returns>
        public static T GetIsolatedStorageSettting<T>(string key)
        {
            T ObjToLoad = default(T);

            if (IsolatedStorageSettings.ApplicationSettings.Contains(key))
            {
                ObjToLoad = (T)IsolatedStorageSettings.ApplicationSettings[key];
            }

            return ObjToLoad;
        }

        /// <summary>
        /// Save object to Isolated Storage Settings
        /// </summary>
        /// <param name="key">Setting key</param>
        /// <param name="value">Setting value</param>
        /// <param name="checkIsPersisted">Check if Setting is persisted</param>
        public static bool SaveIsolatedStorageSetting(string key, object value, bool checkIsPersisted = false)
        {
            var result = true;
            if (!IsolatedStorageSettings.ApplicationSettings.Contains(key))
            {
                IsolatedStorageSettings.ApplicationSettings.Add(key, value);
            }
            else
            {
                IsolatedStorageSettings.ApplicationSettings[key] = value;
            }

            if (checkIsPersisted)
                result = IsIsolatedStorageSettingPersisted(key);

            return result;

        }

        /// <summary>
        /// Check if setting is persisted
        /// </summary>
        /// <param name="key">Setting key</param>
        /// <returns>bool</returns>
        public static bool IsIsolatedStorageSettingPersisted(string key)
        {
            return IsolatedStorageSettings.ApplicationSettings.Contains(key);
        }

        #endregion ## Isolated Storage Setting ##

        #region ## Private Methods ##

        #region ## Object Methods ##

        /// <summary>
        /// Get isolated storage path
        /// </summary>
        /// <typeparam name="TInterface">Object Type</typeparam>
        /// <param name="fileName">File name</param>
        /// <returns>string path</returns>
        private string GetPath<TInterface>(string fileName)
        {
            using (var isf = IsolatedStorageFile.GetUserStoreForApplication())
            {
                var type = typeof(TInterface);
                var pathName = this.Settings.AppStatePrefix + type.Name;
                if (string.IsNullOrEmpty(fileName))
                {
                    fileName = type.Name + ".json";
                }
                else
                {
                    var hasExtension = fileName.EndsWith(".json");
                    if (!hasExtension)
                    {
                        fileName += ".json";
                    }
                }

                if (!string.IsNullOrWhiteSpace(pathName) && !isf.DirectoryExists(pathName))
                    isf.CreateDirectory(pathName);

                var path = Path.Combine(pathName, fileName);

                if (!isf.FileExists(path))
                    isf.CreateFile(path);

                return path;
            }
        }

        /// <summary>
        /// Get Application State key
        /// </summary>
        /// <typeparam name="TInterface">Interface type</typeparam>
        /// <returns>string key</returns>
        private string GetCacheKey<TInterface>(string fileName)
        {
            var key = this.Settings.AppStatePrefix + fileName.Replace("json", string.Empty).Replace(".", string.Empty);
            return key;
        }

        /// <summary>
        /// Internal method for recursive use
        /// </summary>
        /// <typeparam name="TModel"></typeparam>
        /// <typeparam name="TInterface"></typeparam>
        /// <param name="fileName"></param>
        /// <param name="count"></param>
        /// <param name="cacheOnly"></param>
        /// <param name="skipFileEmptyException"></param>
        /// <returns></returns>
        private List<TInterface> LoadObjectListInternal<TModel, TInterface>(string fileName, int count, bool cacheOnly, bool skipFileEmptyException)
            where TInterface : IIcBase
            where TModel : TInterface, new()
        {
            try
            {
                List<TInterface> result = new List<TInterface>();

                var appState = PhoneApplicationService.Current.State;
                var key = this.GetCacheKey<TInterface>();
                var hasKey = appState.ContainsKey(key);
                var isObjCached = this.IsObjectCached<TInterface>();

                if (isObjCached)
                {
                    var objCached = this.GetObjectListCached<TModel, TInterface>();
                    result = objCached;
                }
                else if (hasKey)
                {
                    var resSwap = (IList)appState[key];
                    if (resSwap != null)
                        result = resSwap.Cast<TInterface>().ToList();
                }
                else if (!cacheOnly)
                {
                    var path = GetPath<TInterface>(fileName);

                    IsolatedStorageFile.GetUserStoreForApplication().Dispose();

                    using (var isf = IsolatedStorageFile.GetUserStoreForApplication())
                    using (var fs = new IsolatedStorageFileStream(path, FileMode.OpenOrCreate, isf))
                    using (var stream = new StreamReader(fs))
                    {
                        var json = stream.ReadToEnd();
                        if (!string.IsNullOrWhiteSpace(json))
                        {
                            var isfResult = IcJsonSerializer.Deserialize<IcSmartPersistCacheModel>(json);
                            var jsonObj = isfResult.Obj.ToString();
                            result = IcJsonSerializer.DeserializeList<TModel, TInterface>(jsonObj);
                            appState[key] = result;
                        }

                        stream.Close();
                        fs.Close();
                    }
                }

                if (!skipFileEmptyException && result.Count == 0)
                    throw new FileEmptyException();

                return result.Where(i => i != null).ToList();
            }
            catch (FileEmptyException)
            {
                throw;
            }
            catch (Exception)
            {
                if (count <= this.Settings.MaxAttemptsToAccessISF)
                    return this.LoadObjectListInternal<TModel, TInterface>(fileName, ++count, cacheOnly, skipFileEmptyException);
                else
                    throw;
            }
        }

        /// <summary>
        /// Method to add Object to App State and Object Cache
        /// </summary>
        /// <typeparam name="TModel">Class type</typeparam>
        /// <typeparam name="TInterface">Interface type</typeparam>
        /// <param name="obj">Object to add</param>
        /// <param name="cacheOnly"></param>
        /// <param name="fileName">File name</param>
        private void AddOrUpdateCacheList<TModel, TInterface>(object obj, string fileName, bool cacheOnly)
            where TInterface : IIcBase
            where TModel : TInterface, new()
        {
            try
            {
                var key = this.GetCacheKey(fileName);
                var objToCache = new IcSmartPersistCacheModel();
                objToCache.ClassType = typeof(TModel);
                objToCache.InterfaceType = typeof(TInterface);
                objToCache.Obj = obj;
                objToCache.FileName = fileName;
                objToCache.LastUpdate = DateTime.Now;
                objToCache.CacheOnly = cacheOnly;
                
                var appState = PhoneApplicationService.Current.State;
                appState[key] = obj;

                if (this.IsObjectCached<TInterface>())
                    this.CachedObjects[key] = objToCache;
                else
                    this.CachedObjects.Add(key, objToCache);

                this.CacheImages<TModel, TInterface>(obj);
            }
            catch (Exception) { throw; }
        }

        /// <summary>
        /// Get object list from IcSmartPersistService
        /// </summary>
        /// <typeparam name="TModel">Class type</typeparam>
        /// <typeparam name="TInterface">Interface type</typeparam>
        /// <returns>List of TInterface</returns>
        private List<TInterface> GetObjectListCached<TModel, TInterface>()
            where TInterface : IIcBase
            where TModel : TInterface, new()
        {
            List<TInterface> result = new List<TInterface>();

            var interfaceName = typeof(TInterface).Name;

            if (this.IsObjectCached<TInterface>())
            {
                var objListCached = (IList)this.CachedObjects[interfaceName].Obj;
                result = objListCached.Cast<TInterface>().ToList();
            }

            return result;
        }

        /// <summary>
        /// Get cached object
        /// </summary>
        /// <typeparam name="TModel"></typeparam>
        /// <typeparam name="TInterface"></typeparam>
        /// <returns></returns>
        private IcSmartPersistCacheModel GetCacheObject<TModel, TInterface>()
            where TInterface : IIcBase
            where TModel : TInterface, new()
        {
            var result = new IcSmartPersistCacheModel();
            var interfaceName = typeof(TInterface).Name;

            if (this.IsObjectCached<TInterface>())
            {
                var cacheObj = (IcSmartPersistCacheModel)this.CachedObjects[interfaceName];
                result = cacheObj;
            }

            return result;
        }

        /// <summary>
        /// Persist cached object to Isolated Storage
        /// </summary>
        /// <typeparam name="TModel"></typeparam>
        /// <typeparam name="TInterface"></typeparam>
        /// <param name="fileName"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        private bool PersistObjectInternal<TModel, TInterface>(string fileName, int count = 0)
            where TInterface : IIcBase
            where TModel : TInterface, new()
        {
            try
            {
                this.IsSaving = true;
                var path = GetPath<TInterface>(fileName);

                var obj = this.GetCacheObject<TModel, TInterface>();

                IsolatedStorageFile.GetUserStoreForApplication().Dispose();

                using (var isf = IsolatedStorageFile.GetUserStoreForApplication())
                using (var fs = new IsolatedStorageFileStream(path, FileMode.Truncate, FileAccess.Write, isf))
                using (var stream = new StreamWriter(fs))
                {
                    var json = IcJsonSerializer.Serialize(obj);
                    stream.Write(json);
                }

                return true;
            }
            catch (Exception)
            {
                if (count <= this.Settings.MaxAttemptsToAccessISF)
                    return this.PersistObjectInternal<TModel, TInterface>(fileName, ++count);
                else
                    throw;
            }
            finally
            {
                this.IsSaving = false;
            }
        }

        /// <summary>
        /// Send all Isolated Storage objects to cache
        /// </summary>
        /// <param name="count"></param>
        private void CacheAllIsolatedStorageObjects(int count = 0)
        {
            try
            {
                var result = new Dictionary<string, IcSmartPersistCacheModel>();

                IsolatedStorageFile.GetUserStoreForApplication().Dispose();

                using (var isf = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    var folders = isf.GetDirectoryNames(string.Concat(this.Settings.AppStatePrefix, "*")).ToList();
                    foreach (var folder in folders)
                    {
                        var searchPath = string.Format("{0}\\*.*", folder);
                        var files = isf.GetFileNames(searchPath).ToList<string>();
                        foreach (var file in files)
                        {
                            using (var fs = new IsolatedStorageFileStream(file, FileMode.OpenOrCreate, isf))
                            using (var stream = new StreamReader(fs))
                            {
                                var json = stream.ReadToEnd();

                                if (!string.IsNullOrWhiteSpace(json))
                                {
                                    var isfResult = IcJsonSerializer.Deserialize<IcSmartPersistCacheModel>(json);
                                    result.Add(isfResult.InterfaceType.Name, isfResult);
                                }

                                stream.Close();
                                fs.Close();
                            }
                        }
                    }

                    isf.Dispose();
                }

                this.CachedObjects = result;
            }
            catch (Exception)
            {
                if (count <= this.Settings.MaxAttemptsToAccessISF)
                    this.CacheAllIsolatedStorageObjects(++count);
                else
                    throw;
            }
        }

        #endregion ## Object Methods ##

        #region ## Image Methods ##

        /// <summary>
        /// Set file extension to .jpeg
        /// </summary>
        /// <param name="fileName">File name</param>
        /// <returns></returns>
        private string SetExtension(string fileName)
        {
            var result = Regex.Replace(fileName, "(.gif|.png|.bmp|.jpg|.jpeg)", ".jpeg");
            result = result.EndsWith(".jpeg") ? result : string.Format("{0}.jpeg", result);
            return result;
        }

        /// <summary>
        /// Remove file extension
        /// </summary>
        /// <param name="fileName">File name</param>
        /// <returns></returns>
        private string RemoveExtension(string fileName)
        {
            var result = Regex.Replace(fileName, ".jpeg", string.Empty);
            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TModel"></typeparam>
        /// <typeparam name="TInterface"></typeparam>
        /// <param name="obj"></param>
        private void CacheImages<TModel, TInterface>(object obj)
            where TInterface : IIcBase
            where TModel : TInterface, new()
        {
            var type = typeof(TModel);
            var props = type.GetProperties().Where(i => i.PropertyType.Equals(typeof(Uri))).ToList();
            IList list;
            if (obj is IList)
            {
                list = ((IList)obj);
            }
            else
            {
                list = new List<TInterface> { (TModel)obj };
            }

            foreach (var item in list)
            {
                foreach (var prop in props)
                {
                    var attrs = prop.GetCustomAttributes(true).Where(i => i.GetType() == typeof(CacheImage)).ToList();
                    foreach (var attr in attrs)
                    {
                        var url = prop.GetValue(item, null) as Uri;
                        this.CacheImage(url, null);
                    }
                }
            }
        }

        /// <summary>
        /// Cache image by Uri
        /// </summary>
        /// <param name="url"></param>
        /// <param name="callback"></param>
        private void CacheImage(Uri url, Action<IcSmartPersistImageCacheModel, Exception> callback)
        {
            if (url != null)
            {
                IcSmartPersistImageCacheModel result;
                if (this.CachedImages.TryGetValue(url.GetImageName(), out result))
                {
                    if (callback != null && result != null)
                        callback(result, null);
                }
                else
                {
                    AsyncCall.Get.Bytes(url, (bytes, contentType, ex) =>
                    {
                        if (ex != null)
                        {
                            if (callback != null)
                                callback(null, ex);
                        }
                        else
                        {
                            var asyncResult = new IcSmartPersistImageCacheModel();
                            asyncResult.FileName = url.GetImageName();
                            asyncResult.MimeType = contentType.GetMimeType();
                            asyncResult.Bytes = bytes;
                            this.CachedImages[asyncResult.FileName] = asyncResult;

                            if (callback != null)
                                callback(asyncResult, null);
                        }
                    });
                }
            }
        }

        /// <summary>
        /// Persist cached object to Isolated Storage
        /// </summary>
        /// <param name="count"></param>
        /// <returns></returns>
        private bool PersistImagesInternal(int count = 0)
        {
            try
            {
                this.IsSaving = true;

                if (this.CachedImages.Count > 0)
                {
                    IsolatedStorageFile.GetUserStoreForApplication().Dispose();

                    using (var isf = IsolatedStorageFile.GetUserStoreForApplication())
                    {
                        foreach (var item in this.CachedImages)
                        {
                            IsolatedStorageFileStream fileStream;
                            var fileName = this.SetExtension(item.Key);

                            var folderPath = this.Settings.ImagesCacheFolderName;
                            if (!string.IsNullOrWhiteSpace(folderPath) && !isf.DirectoryExists(folderPath))
                                isf.CreateDirectory(folderPath);

                            var path = Path.Combine(folderPath, fileName);
                            if (isf.FileExists(path))
                            {
                                isf.DeleteFile(path);
                            }

                            fileStream = isf.CreateFile(path);

                            if (fileStream != null)
                            {
                                var image = new BitmapImage();
                                image.SetSource(new MemoryStream(item.Value.Bytes));
                                WriteableBitmap wb = new WriteableBitmap(image);
                                //TODO: convert to jpeg all images with different mime type
                                Extensions.SaveJpeg(wb, fileStream, wb.PixelWidth, wb.PixelHeight, 0, 85);
                                fileStream.Close();
                                fileStream.Dispose();
                            }
                        }
                    }
                }

                return true;
            }
            catch (Exception)
            {
                if (count <= this.Settings.MaxAttemptsToAccessISF)
                    return this.PersistImagesInternal(++count);
                else
                    throw;
            }
            finally
            {
                this.IsSaving = false;
            }
        }

        /// <summary>
        /// Send all Isolated Storage images to cache
        /// </summary>
        /// <param name="count"></param>
        private void CacheAllIsolatedStorageImages(int count = 0)
        {
            try
            {
                var result = new Dictionary<string, IcSmartPersistImageCacheModel>();

                IsolatedStorageFile.GetUserStoreForApplication().Dispose();

                using (var isf = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    var imagesFolderPath = this.Settings.ImagesCacheFolderName;
                    var folders = isf.GetDirectoryNames(imagesFolderPath).ToList();
                    foreach (var folder in folders)
                    {
                        var searchPath = string.Format("{0}\\*.jpeg", imagesFolderPath);
                        var files = isf.GetFileNames(searchPath).ToList<string>();
                        foreach (var file in files)
                        {
                            BitmapImage bi = new BitmapImage();
                            using (var fs = new IsolatedStorageFileStream(file, FileMode.OpenOrCreate, isf))
                            {
                                bi.SetSource(fs);

                                var cacheModel = new IcSmartPersistImageCacheModel();
                                cacheModel.Bytes = bi.GetBytes();
                                cacheModel.FileName = file;
                                cacheModel.MimeType = MimeType.Jpeg;

                                var key = this.RemoveExtension(file);

                                result.Add(key, cacheModel);
                                fs.Close();
                            }
                        }
                    }

                    isf.Dispose();
                }

                this.CachedImages = result;
            }
            catch (Exception)
            {
                if (count <= this.Settings.MaxAttemptsToAccessISF)
                    this.CacheAllIsolatedStorageImages(++count);
                else
                    throw;
            }
        }

        #endregion ## Image Methods ##

        #endregion ## Private Methods ##
    }
}
